package is.glif.fenius.ws.connection.impl;

import is.glif.fenius.api.FActivationStatus;
import is.glif.fenius.api.FAvailReq;
import is.glif.fenius.api.FAvailResp;
import is.glif.fenius.api.FCancelReq;
import is.glif.fenius.api.FCancelResp;
import is.glif.fenius.api.FCreateReq;
import is.glif.fenius.api.FCreateResp;
import is.glif.fenius.api.FExternalFault;
import is.glif.fenius.api.FInternalFault;
import is.glif.fenius.api.FListReq;
import is.glif.fenius.api.FListResp;
import is.glif.fenius.api.FQueryReq;
import is.glif.fenius.api.FQueryResp;
import is.glif.fenius.api.FReservation;
import is.glif.fenius.api.FReservationReqParams;
import is.glif.fenius.api.FReservationStatus;
import is.glif.fenius.api.Translator;
import is.glif.fenius.api.TranslatorFactory;
import is.glif.fenius.ws.connection.gen.ActivationStatus;
import is.glif.fenius.ws.connection.gen.Advance;
import is.glif.fenius.ws.connection.gen.Avp;
import is.glif.fenius.ws.connection.gen.AvpSeq;
import is.glif.fenius.ws.connection.gen.Edge;
import is.glif.fenius.ws.connection.gen.IsReservable;
import is.glif.fenius.ws.connection.gen.IsReservableReq;
import is.glif.fenius.ws.connection.gen.IsReservableResp;
import is.glif.fenius.ws.connection.gen.IsReservableResponse;
import is.glif.fenius.ws.connection.gen.ListFilter;
import is.glif.fenius.ws.connection.gen.ListReq;
import is.glif.fenius.ws.connection.gen.ListResp;
import is.glif.fenius.ws.connection.gen.ListResponse;
import is.glif.fenius.ws.connection.gen.QueryReq;
import is.glif.fenius.ws.connection.gen.QueryResp;
import is.glif.fenius.ws.connection.gen.QueryResponse;
import is.glif.fenius.ws.connection.gen.Release;
import is.glif.fenius.ws.connection.gen.ConnectionService;
import is.glif.fenius.ws.connection.gen.ReleaseReq;
import is.glif.fenius.ws.connection.gen.ReleaseResp;
import is.glif.fenius.ws.connection.gen.ReleaseResponse;
import is.glif.fenius.ws.connection.gen.Reservable;
import is.glif.fenius.ws.connection.gen.ReservationStatus;
import is.glif.fenius.ws.connection.gen.Reserve;
import is.glif.fenius.ws.connection.gen.ReserveReq;
import is.glif.fenius.ws.connection.gen.ReserveResp;
import is.glif.fenius.ws.connection.gen.ReserveResponse;
import is.glif.fenius.ws.connection.gen.ExternalFault_Exception;
import is.glif.fenius.ws.connection.gen.InternalFault_Exception;
import is.glif.fenius.ws.connection.gen.Query;
import is.glif.fenius.ws.connection.gen.Reservation;
import is.glif.fenius.ws.connection.gen.Schedule;
import is.glif.fenius.ws.connection.gen.Topology;
import is.glif.fenius.ws.connection.gen.Vertex;

import java.util.ArrayList;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.List;

import javax.xml.datatype.DatatypeFactory;
import javax.xml.datatype.DatatypeConfigurationException;
import javax.xml.datatype.XMLGregorianCalendar;

import org.apache.log4j.Logger;

@SuppressWarnings("restriction")
@javax.jws.WebService(
        serviceName = "connection",
        portName = "ConnectionServicePort",
        targetNamespace = "http://fenius.glif.is/ws/connection/v1/service",
        endpointInterface = "is.glif.fenius.ws.connection.gen.ConnectionService")
public class ConnectionServiceImpl implements ConnectionService {

    private static final Logger log = Logger.getLogger(ConnectionServiceImpl.class.getName());

    public is.glif.fenius.ws.connection.gen.ReleaseResponse release(Release parameters) throws InternalFault_Exception , ExternalFault_Exception    {
        log.info("Executing operation release");
        System.out.println(parameters);
        try {
            // first get the translator.
            Translator t = TranslatorFactory.getInstance().getTranslator();
            if (t == null) {
                InternalFault_Exception ex = new InternalFault_Exception("Could not locate translator service");
                throw ex;
            }

            // then dig into the request all the way down to...
            ReleaseReq soapReq = parameters.getArg0();
            if (soapReq == null) {
                throw new ExternalFault_Exception("received null ReleaseReq");
            }
            // the reservation id
            String reservationId = soapReq.getReservationId();
            if (reservationId == null) {
                throw new ExternalFault_Exception("received null id");
            }

            // do internal wrapping
            FCancelReq feniusReq = new FCancelReq();
            feniusReq.setReservationId(reservationId);

            // call the translator cancel() function
            FCancelResp feniusResp = t.cancel(feniusReq);

            // dig through the wrappers until we get the actual response
            if (feniusResp == null) {
                throw new InternalFault_Exception("translator returned null response");
            }

            ReleaseResp soapResp = new ReleaseResp();
            ReservationStatus st = ReservationStatus.fromValue(feniusResp.getReservationStatus().value());

            // perhaps more mapping here?
            soapResp.setReservationStatus(st);

            // do external wrapping and..
            ReleaseResponse respWrapper = new ReleaseResponse();
            respWrapper.setReturn(soapResp);

            // return to caller
            return respWrapper;
        } catch (FInternalFault ex) {
            throw new InternalFault_Exception(ex.getMessage());
        } catch (FExternalFault ex) {
            throw new ExternalFault_Exception(ex.getMessage());
        } catch (Exception ex) {
            ex.printStackTrace();
            throw new InternalFault_Exception(ex.getMessage());
        }
    }

    public is.glif.fenius.ws.connection.gen.ReserveResponse reserve(Reserve parameters) throws InternalFault_Exception , ExternalFault_Exception    {
        log.info("Executing operation reserve");
        System.out.println(parameters);
        try {
            // first get the translator.
            Translator t = TranslatorFactory.getInstance().getTranslator();
            if (t == null) {
                InternalFault_Exception ex = new InternalFault_Exception("Could not locate translator service");
                throw ex;
            }

            // then dig into the request all the way down to...
            ReserveReq soapReq = parameters.getArg0();
            if (soapReq == null) {
                throw new ExternalFault_Exception("received null CreateReq");
            }
            // the reservation object
            Reservable soapReqResv = soapReq.getReservationParams();
            if (soapReqResv == null) {
                throw new ExternalFault_Exception("received null reservation");
            }

            // map it to the internal API reservation
            FReservationReqParams feniusReqResv = this.mapSoapResvReqToFenius(soapReqResv);

            // do internal wrapping
            FCreateReq feniusReq = new FCreateReq();
            feniusReq.setResvParams(feniusReqResv);

            // call the translator create() function
            FCreateResp feniusResp = t.create(feniusReq);

            // dig through the wrappers until..
            if (feniusResp == null) {
                throw new InternalFault_Exception("translator returned null response");
            }
            // you get the reservation response
            FReservation feniusRespResv = feniusResp.getResvParams();
            if (feniusRespResv == null) {
                throw new InternalFault_Exception("translator returned null reservation");
            }

            // then map it back to SOAP
            Reservation soapRespResv = this.mapFeniusResvToSoap(feniusRespResv);
            ReserveResp soapResp = new ReserveResp();
            soapResp.setReservation(soapRespResv);

            // do external wrapping and..
            ReserveResponse respWrapper = new ReserveResponse();
            respWrapper.setReturn(soapResp);

            // return to caller
            return respWrapper;
        } catch (FInternalFault ex) {
            throw new InternalFault_Exception(ex.getMessage());
        } catch (FExternalFault ex) {
            throw new ExternalFault_Exception(ex.getMessage());
        } catch (Exception ex) {
            ex.printStackTrace();
            throw new InternalFault_Exception(ex.getMessage());
        }
    }


    public is.glif.fenius.ws.connection.gen.QueryResponse query(Query parameters) throws InternalFault_Exception , ExternalFault_Exception    {
        log.info("Executing operation query");
        System.out.println(parameters);
        try {
            // first get the translator.
            Translator t = TranslatorFactory.getInstance().getTranslator();
            if (t == null) {
                InternalFault_Exception ex = new InternalFault_Exception("Could not locate translator service");
                throw ex;
            }

            // then dig into the request all the way down to...
            QueryReq soapReq = parameters.getArg0();
            if (soapReq == null) {
                throw new ExternalFault_Exception("received null QueryReq");
            }
            // the reservation id
            String reservationId = soapReq.getReservationId();
            if (reservationId == null) {
                throw new ExternalFault_Exception("received null id");
            }

            // do internal wrapping
            FQueryReq feniusReq = new FQueryReq();
            feniusReq.setReservationId(reservationId);

            // call the translator query() function
            FQueryResp feniusResp = t.query(feniusReq);

            // dig through the wrappers until we get the actual response
            if (feniusResp == null) {
                throw new InternalFault_Exception("translator returned null response");
            }


            QueryResp soapResp = new QueryResp();
            FReservation feniusRespResv = feniusResp.getResvParams();
            Reservation soapRespResv = this.mapFeniusResvToSoap(feniusRespResv);

            // perhaps more mapping here?
            soapResp.setReservation(soapRespResv);

            // do external wrapping and..
            QueryResponse respWrapper = new QueryResponse();
            respWrapper.setReturn(soapResp);

            // return to caller
            return respWrapper;
        } catch (FInternalFault ex) {
            throw new InternalFault_Exception(ex.getMessage());
        } catch (FExternalFault ex) {
            throw new ExternalFault_Exception(ex.getMessage());
        } catch (Exception ex) {
            ex.printStackTrace();
            throw new InternalFault_Exception(ex.getMessage());
        }
    }

    public IsReservableResponse isReservable(IsReservable parameters)
            throws InternalFault_Exception, ExternalFault_Exception {
        log.info("Executing operation IsReservable");
        System.out.println(parameters);
        try {
            // first get the translator.
            Translator t = TranslatorFactory.getInstance().getTranslator();
            if (t == null) {
                InternalFault_Exception ex = new InternalFault_Exception("Could not locate translator service");
                throw ex;
            }

            // then dig into the request all the way down to...
            IsReservableReq soapReq = parameters.getArg0();
            if (soapReq == null) {
                throw new ExternalFault_Exception("received null isReservableReq");
            }
            // the reservation object
            Reservable soapReqResv = soapReq.getReservationParams();
            if (soapReqResv == null) {
                throw new ExternalFault_Exception("received null reservation");
            }

            // map it to the internal API reservation
            FReservationReqParams feniusReqResv = this.mapSoapResvReqToFenius(soapReqResv);

            // do internal wrapping
            FAvailReq feniusReq = new FAvailReq();
            feniusReq.setResvParams(feniusReqResv);

            // call the translator create() function
            FAvailResp feniusResp = t.isAvailable(feniusReq);

            // dig through the wrappers until..
            if (feniusResp == null) {
                throw new InternalFault_Exception("translator returned null response");
            }
            boolean result = feniusResp.isAvailable();


            // then map it back to SOAP
            IsReservableResp soapResp = new IsReservableResp();
            soapResp.setResult(result);

            // do external wrapping and..
            IsReservableResponse respWrapper = new IsReservableResponse();
            respWrapper.setReturn(soapResp);

            // return to caller
            return respWrapper;
        } catch (FInternalFault ex) {
            throw new InternalFault_Exception(ex.getMessage());
        } catch (FExternalFault ex) {
            throw new ExternalFault_Exception(ex.getMessage());
        } catch (Exception ex) {
            ex.printStackTrace();
            throw new InternalFault_Exception(ex.getMessage());
        }
    }


    public is.glif.fenius.ws.connection.gen.ListResponse list(is.glif.fenius.ws.connection.gen.List parameters) throws InternalFault_Exception , ExternalFault_Exception    {
        log.info("Executing operation list");
        System.out.println(parameters);
        try {
            Translator t = TranslatorFactory.getInstance().getTranslator();
            if (t == null) {
                InternalFault_Exception ex = new InternalFault_Exception("Could not locate translator service");
                throw ex;
            }
            ListReq listReq = parameters.getArg0();
            FListReq freq = this.mapListReqToFenius(listReq);
            
            FListResp fresp = t.list(freq);
            ListResp resp = new ListResp();
            
            for (FReservation fresv : fresp.getReservations())  {
                Reservation resv = this.mapFeniusResvToSoap(fresv);
                resp.getReservations().add(resv);
            }
            
            ListResponse _return = new ListResponse();
            _return.setReturn(resp);
            return _return;
        } catch (FInternalFault ex) {
            throw new InternalFault_Exception(ex.getMessage());
        } catch (FExternalFault ex) {
            throw new ExternalFault_Exception(ex.getMessage());
        } catch (Exception ex) {
            ex.printStackTrace();
            throw new InternalFault_Exception(ex.getMessage());
        }
    }
    
    
    
    protected FReservationReqParams mapSoapResvReqToFenius(Reservable resv) throws FInternalFault {
        FInternalFault iFault = new FInternalFault();
        if (resv == null) {
            iFault.setMessage("null reservation");
            throw iFault;
        }
        Schedule sch = resv.getSchedule();
        if (sch == null) {
            iFault.setMessage("null schedule");
            throw iFault;
        }
        Date activationTime = null;
        Integer duration = null;
        Advance adv = sch.getAdvance();
        if (adv != null) {
            if (adv.getStart() != null) {
                activationTime = adv.getStart().toGregorianCalendar().getTime();
                log.debug("activation: "+activationTime);
            } else {
                iFault.setMessage("start time not set for advance schedule");
                throw iFault;
            }

            if (adv.getDuration() != null) {
                duration = adv.getDuration();
            } else if (adv.getEnd() != null) {
                Long startMs = adv.getEnd().toGregorianCalendar().getTimeInMillis();
                Long endMs   = adv.getEnd().toGregorianCalendar().getTimeInMillis();
                Long diff = endMs - startMs;
                if (diff < 0) {
                    iFault.setMessage("end time after start time");
                    throw iFault;
                } else {
                    // convert to seconds
                    duration = diff.intValue() / 1000;
                    log.debug("duration: "+duration);
                }

            } else {
                iFault.setMessage("neither duration or end time set for advance schedule");
                throw iFault;
            }

        } else if (sch.getImmediate() != null) {
            activationTime = new Date(); // now
            iFault.setMessage("immediate not supported yet");
            throw iFault;

        } else {
            iFault.setMessage("neither immediate or advance schedule set");
            throw iFault;
        }


        Topology topo = resv.getTopology();
        if (topo == null) {
            iFault.setMessage("no topology set");
            throw iFault;
        }
        List<Vertex> vertices = topo.getVertices();
        List<Edge> edges = topo.getEdges();
        if (vertices == null) {
            iFault.setMessage("no vertices in topology, exactly 2 needed");
            throw iFault;
        } else if (edges == null) {
            iFault.setMessage("no edges in topology, exactly 1 needed");
            throw iFault;
        }
        if (vertices.size() != 2) {
            iFault.setMessage("fenius translator only supports exactly 2 vertices");
            throw iFault;
        }
        if (edges.size() != 1) {
            iFault.setMessage("fenius translator only supports exactly 1 edge");
            throw iFault;
        }
        Edge e = edges.get(0);
        Integer bandwidth = e.getBandwidth();
        Vertex a = vertices.get(0);
        Vertex z = vertices.get(1);
        String srcEndpoint = a.getResourceId();
        String dstEndpoint = z.getResourceId();
        log.debug("bandwidth: "+bandwidth);
        log.debug("srcEndpoint: "+srcEndpoint);
        log.debug("dstEndpoint: "+dstEndpoint);

        AvpSeq aTech = a.getTechParams();
        AvpSeq zTech = z.getTechParams();
        if (aTech == null || aTech.getAvp() == null || aTech.getAvp().isEmpty()) {
            iFault.setMessage("no tech params for 1st vertex");
            throw iFault;
        } else if (zTech == null || zTech.getAvp() == null || zTech.getAvp().isEmpty()) {
            iFault.setMessage("no tech params for 2nd vertex");
            throw iFault;
        }
        String srcVlanTag = null;
        String dstVlanTag = null;
        for (Avp avp : aTech.getAvp()) {
            String name = avp.getAttrName();
            String val = avp.getAttrVal();
            log.debug("atech avp: "+name+" -- "+val);
            if (name.equals("eth.vlanTag")) {
                srcVlanTag = val;
            }
        }
        
        for (Avp avp : zTech.getAvp()) {
            String name = avp.getAttrName();
            String val = avp.getAttrVal();
            log.debug("zTech avp: "+name+" -- "+val);
            if (name.equals("eth.vlanTag")) {
                dstVlanTag = val;
            }
        }
        if (srcVlanTag == null) {
            iFault.setMessage("no eth.vlanTag tech param for 1st vertex");
            throw iFault;
        } else if (dstVlanTag == null) {
            iFault.setMessage("no eth.vlanTag tech param for 2nd vertex");
            throw iFault;
        }
        ArrayList<String> descriptions = new ArrayList<String>();
        for (String desc : resv.getDescription()) {
            descriptions.add(desc);
        }
        for (String tag : resv.getTag()) {
            descriptions.add(tag);
        }
        String[] descriptors = new String[descriptions.size()];
        for (int i = 0; i < descriptions.size(); i++) {
            descriptors[i] = descriptions.get(i);
        }

        FReservationReqParams reqParams = new FReservationReqParams();
        reqParams.setActivationTime(activationTime);
        reqParams.setDuration(duration);
        
        reqParams.setDescriptors(descriptors);
        reqParams.setBandwidth(bandwidth);
        reqParams.setDstEndpoint(dstEndpoint);
        reqParams.setSrcEndpoint(srcEndpoint);
        reqParams.setSrcVlanTag(srcVlanTag);
        reqParams.setDstVlanTag(dstVlanTag);
        return reqParams;
    }
    
    protected ReservationStatus mapFResvStatus(FReservationStatus frs) {
        if (frs == null) return null;
        if (frs.equals(FReservationStatus.FAILED)) {
            return ReservationStatus.FAILED;
        } else if (frs.equals(FReservationStatus.RESERVING)) {
            return ReservationStatus.RESERVING;
        } else if (frs.equals(FReservationStatus.RESERVED)) {
            return ReservationStatus.RESERVED;
        } else if (frs.equals(FReservationStatus.RELEASING)) {
            return ReservationStatus.RELEASING;
        } else if (frs.equals(FReservationStatus.RELEASED)) {
            return ReservationStatus.RELEASED;
        } else if (frs.equals(FReservationStatus.FINISHING)) {
            return ReservationStatus.FINISHING;
        } else if (frs.equals(FReservationStatus.FINISHED)) {
            return ReservationStatus.FINISHED;
        }
        return null;
    }
    
    protected ActivationStatus mapFActStatus(FActivationStatus fas) {
        if (fas == null) return null;
        if (fas.equals(FActivationStatus.FAILED)) {
            return ActivationStatus.FAILED;
        } else if (fas.equals(FActivationStatus.ACTIVATING)) {
            return ActivationStatus.ACTIVATING;
        } else if (fas.equals(FActivationStatus.ACTIVE)) {
            return ActivationStatus.ACTIVE;
        } else if (fas.equals(FActivationStatus.DEACTIVATING)) {
            return ActivationStatus.DEACTIVATING;
        } else if (fas.equals(FActivationStatus.INACTIVE)) {
            return ActivationStatus.INACTIVE;
            
        }
        return null;
    }
    
    
    protected Reservation mapFeniusResvToSoap(FReservation resv) throws InternalFault_Exception {
        String src = resv.getSrcEndpoint();
        String dst = resv.getDstEndpoint();
        String srcVlan = resv.getSrcVlanTag();
        String dstVlan = resv.getDstVlanTag();
        Integer bandwidth = resv.getBandwidth();
        Date activation = resv.getActivationTime();
        Integer duration = resv.getDuration();
        
        
        FActivationStatus actStat = resv.getActivationStatus();
        FReservationStatus resvStat = resv.getReservationStatus();
        String[] descriptors = resv.getDescriptors();
        String resvId = resv.getReservationId();
        
        Reservation res = new Reservation();
        res.setReservationId(resvId);
        ActivationStatus as = this.mapFActStatus(actStat);
        ReservationStatus rs = this.mapFResvStatus(resvStat);
        res.setActivationStatus(as);
        res.setReservationStatus(rs);
        
        log.debug("a: "+src);
        log.debug("z: "+dst);
        log.debug("bw: "+bandwidth);
        log.debug("act: "+activation);
        log.debug("dur: "+duration);
        log.debug("sv: "+srcVlan);
        log.debug("dv: "+dstVlan);
        
        
        
        // set topology
        Topology topo = new Topology();
        topo.setDirected(false);

        Vertex a = new Vertex();
        a.setVertexId("vertexA");
        a.setResourceId(src);
        Vertex z = new Vertex();
        z.setVertexId("vertexZ");
        z.setResourceId(dst);

        topo.getVertices().add(a);
        topo.getVertices().add(z);
        
        Edge e = new Edge();
        e.setA("vertexA");
        e.setBandwidth(bandwidth);
        e.setZ("vertexZ");
        topo.getEdges().add(e);
        
        res.setTopology(topo);

        
        if (srcVlan == null) srcVlan = "";
        if (dstVlan == null) dstVlan = "";
        
        // set ethernet params
        Avp srcVlanValuePair = new Avp();
        srcVlanValuePair.setAttrName("eth.vlanTag");
        srcVlanValuePair.setAttrVal(srcVlan);
        AvpSeq aTechParams = new AvpSeq();
        aTechParams.getAvp().add(srcVlanValuePair);
        a.setTechParams(aTechParams);
        
        
        Avp dstVlanValuePair = new Avp();
        dstVlanValuePair.setAttrName("eth.vlanTag");
        dstVlanValuePair.setAttrVal(dstVlan);
        AvpSeq zTechParams = new AvpSeq();
        zTechParams.getAvp().add(dstVlanValuePair);
        z.setTechParams(zTechParams);
        
        
        
        

        Schedule sched = new Schedule();
		DatatypeFactory factory = null;
		try {
			factory = DatatypeFactory.newInstance();
		} catch (DatatypeConfigurationException dce) {
			throw new InternalFault_Exception(dce.getMessage());
		}

		if (activation != null) {
			GregorianCalendar gcUp = (GregorianCalendar) GregorianCalendar.getInstance();
			gcUp.setTime(activation);
        
			XMLGregorianCalendar xmlUp = factory.newXMLGregorianCalendar(gcUp);

			Advance adv = new Advance();
			adv.setStart(xmlUp);
			adv.setDuration(duration);

			sched.setAdvance(adv);
		}
        
        GregorianCalendar gcNow = (GregorianCalendar) GregorianCalendar.getInstance();
        XMLGregorianCalendar xmlNow = factory.newXMLGregorianCalendar(gcNow);

        sched.setCreated(xmlNow);
        res.setSchedule(sched);

        if (descriptors != null) {
            for (int i = 0; i < descriptors.length; i++) {
                res.getDescription().add(descriptors[i]);
            }
        }
        
        
        return res;
    }
    
    
    protected FListReq mapListReqToFenius(ListReq req) {
        FListReq freq = new FListReq();
        ListFilter filt = req.getListFilter();
        if (filt == null) {
            return freq;
        }
        if (filt.getReservationId() != null && !filt.getReservationId().isEmpty()) {
            ArrayList<String> resvIds = new ArrayList<String>();
            for (String resvId : filt.getReservationId()) {
                resvIds.add(resvId);
            }
            freq.setReservationIds(resvIds);
        }
        
        if (filt.getDescriptor() != null && !filt.getDescriptor().isEmpty()) {
            ArrayList<String> descriptors = new ArrayList<String>();
            for (String resvId : filt.getDescriptor()) {
                descriptors.add(resvId);
            }
            freq.setDescriptors(descriptors);
        }
        
        if (filt.getStartsBefore() != null) {
            Date startingBefore = filt.getStartsBefore().toGregorianCalendar().getTime();
            freq.setStartingBefore(startingBefore);
        }
        if (filt.getStartsAfter() != null) {
            Date startingAfter = filt.getStartsAfter().toGregorianCalendar().getTime();
            freq.setStartingAfter(startingAfter);
        }
        if (filt.getEndsBefore() != null) {
            Date endingBefore = filt.getEndsBefore().toGregorianCalendar().getTime();
            freq.setEndingBefore(endingBefore);
        }
        if (filt.getEndsAfter() != null) {
            Date endingAfter = filt.getEndsAfter().toGregorianCalendar().getTime();
            freq.setEndingAfter(endingAfter);
        }
        
        return freq;
    }

}
